define(function(require, exports, module) {

  // var Backbone = require('backbone');
  var $ = require('jquery');
  var _ = require('lodash');
  // var jsonview = require('jsonview');
  var tmpl = require('text!../tmpl.html');
  
  require('hy5b/frontend/canvas/index')
  require('hy5b/frontend/canvas/line')

  var THREE = require('three');
  window.THREE = THREE

  var { brightPointCanvas } = require('js/graph/graph')
  var graphUtil = require("hy5b/graph/index")
  var mathUtil = require('hy5b/math/index') 
  var manualControlHelp = require('hy5b/animation/manualControlHelp')
  var { cities } = require('./map')

  var animation = require('hy5b/frontend/animation/index')
  var littleCanvas = require('hy5b/frontend/canvas/littleCanvas')

  // var textureLoader = new THREE.TextureLoader();

  function main() {
    var $app = $('<div id="app">');
    $('#main').html($app);

    $app.append(tmpl);

    require([
      '../../topo/js/loadObjMtl',
      '/d/threejs-0.106.2/examples/js/postprocessing/Pass',
      '/d/threejs-0.106.2/examples/js/lines/LineSegmentsGeometry',
      '/d/threejs-0.106.2/examples/js/lines/LineSegments2',

      '/d/threejs-0.106.2/examples/js/shaders/LuminosityHighPassShader',
      '/d/threejs-0.106.2/examples/js/shaders/CopyShader',
      '/d/threejs-0.106.2/examples/js/postprocessing/ShaderPass',
      '/d/threejs-0.106.2/examples/js/postprocessing/EffectComposer',
      '/d/threejs-0.106.2/examples/js/postprocessing/RenderPass',
      '/d/threejs-0.106.2/examples/js/postprocessing/UnrealBloomPass',

      '/d/threejs-0.106.2/examples/js/lines/Line2',
      '/d/threejs-0.106.2/examples/js/lines/LineGeometry',
      '/d/threejs-0.106.2/examples/js/lines/LineMaterial'
    ], function(getLoadObjMtl) {
      loadObjMtl = getLoadObjMtl();
      new app($app[0], loadObjMtl)
    })
  }

  var downLineGroupMinCount = 60
  var FRAME60 = animation.FRAME60

  //动画共分4个部分
  // 1. 光线下落 15
  // 2. 撞击辐射 23
  // 3. 城市标签 first 43, others 70
  // 4. 涟漪 50
  // 5. 飞线动画 172
  // 6. 相机 15
  var FRAME24 = 1000 / 24
  var downLinesTime = 15 * FRAME24
  var downLinesDelay = downLinesTime
  var downLinesDuration = 32 * FRAME24;

  var bumpTime = 23 * FRAME24
  var bumpDelay = bumpTime

  var cityLabelTime = 43 * FRAME24
  var firstCityLabelBgDelay = cityLabelTime
  var firstCityLabelBgDuration = 4 * FRAME24
  var firstCityLabelTextDelay = cityLabelTime + firstCityLabelBgDuration
  var firstCityLabelTextDuration = 13 * FRAME24

  var cityLabelTime = 70 * FRAME24
  var cityLabelPlaneTriangleDelay = cityLabelTime + 8 * FRAME24
  var cityLabelPlaneTriangleDuration = 8 * FRAME24
  var cityLabelPlaneTriangleOpacityDelay = cityLabelPlaneTriangleDelay
  var cityLabelPlaneTriangleOpacityDuration = 4 * FRAME24
  var cityLabelPlaneCenterTriangleDelay = cityLabelTime + 4 * FRAME24
  var cityLabelPlaneCenterTriangleDuration = 60 * FRAME24
  var cityLabelPlaneTriangleEdgeCicleDelay = cityLabelPlaneTriangleDelay
  var cityLabelPlaneTriangleEdgeCicleDuration = cityLabelPlaneTriangleOpacityDuration // 三角形外沿三段弧移动到位
  var cityLabelPlaneTriangleEdgeCicleRotateDeley = cityLabelPlaneTriangleEdgeCicleDelay + cityLabelPlaneTriangleEdgeCicleDuration
  var cityLabelPlaneTriangleEdgeCicleRotateDuration = 3000
  var cityLabelPlaneCenterPointDelay = cityLabelTime
  var cityLabelPlaneCenterPointDuration = FRAME24
  var cityLabelPlaneCenterPointFlashDelay = cityLabelPlaneCenterPointDelay + 16 * FRAME24
  var cityLabelPlaneCenterPointFlashDuration = 500
  var cityLabelPillarDelay = cityLabelTime
  var cityLabelPillarHighDuration = 5 * FRAME24
  var cityLabelPillarDuration = 10 * FRAME24
  var cityLabelTextBgBorderDelay = cityLabelTime
  var cityLabelTextBgBorderDuration = FRAME24
  var cityLabelTextBgDelay = cityLabelTime
  var cityLabelTextBgDuration = 20 * FRAME24
  var cityLabelTextDelay = cityLabelTime + 4 * FRAME24
  var cityLabelTextDuration = 20 * FRAME24
  var cityLabelInterval = 4 * FRAME24

  var rippleTime = 50 * FRAME24
  var rippleDelay = rippleTime
  var rippleDuration = 1000

  var flyLineTime = 172 * FRAME24
  var flyLineDelay = flyLineTime
  var flyLineDuration = 52 * FRAME24;

  var cameraTime = 15 * FRAME24
  var cameraMoveDelay = cameraTime
  var cameraMove1Duration = 106 * FRAME24
  var cameraMoveDuration = 260 * FRAME24

  var triangleCircleSize = 18

  var animateProps = {
    // down line animate
    downHeight: {
      start: 10,
      end: 0,
      duration: downLinesDuration / 2,
      delay: downLinesDelay
    },
    brightPointsOpacity: {
      start: 1,
      end: 0,
      duration: downLinesDuration / 2,
      delay: downLinesDelay + downLinesDuration / 2
    },
    eraseLinePointsCount: {
      start: 0,
      end: downLineGroupMinCount * 2,
      duration: downLinesDuration / 2,
      delay: downLinesDelay + downLinesDuration / 2
    },

    //city label pillar

    pillarHeight: {
      start: 0,
      end: 2.5,
      duration: cityLabelPillarDuration,
      delay: cityLabelPillarDelay,
      getEasingFuc: function(config) {
        return function(t) {
          if (t < cityLabelPillarHighDuration) {
            return config.start + (2.7 - config.start) * t / cityLabelPillarHighDuration;
          } else {
            return 2.7 + (config.end - 2.7) * (t - cityLabelPillarHighDuration) / (config.duration - cityLabelPillarHighDuration);
          }
        }
      }
    },

    pillarHeadScale: {
      start: 0,
      end: 1,
      duration: cityLabelPillarHighDuration,
      delay: cityLabelPillarDelay
    },

    // city label

    cityLabelBorder: {
      start: 0,
      end: 1,
      duration: cityLabelTextBgBorderDuration,
      delay: cityLabelTextBgBorderDelay,
    },

    cityLabelBgOpacity: {
      start: 0,
      end: 0.7,
      duration: cityLabelTextBgDuration,
      delay: cityLabelTextBgDelay,
    },

    cityLabelText: {
      start: 0,
      end: 1,
      duration: cityLabelTextDuration,
      delay: cityLabelTextDelay
    },

    // city point animate
    centerPoint: {
      start: 0,
      end: 1,
      duration: cityLabelPlaneCenterPointDuration,
      delay: cityLabelPlaneCenterPointDelay
    },

    centerPointFlash: {
      start: 0,
      end: 1,
      duration: cityLabelPlaneCenterPointFlashDuration,
      delay: cityLabelPlaneCenterPointFlashDelay,
      backtrace: true,
      infinity: true
    },

    centerTriangleRotate: {
      start: 0,
      end: Math.PI / 2,
      infinity: true,
      duration: cityLabelPlaneCenterTriangleDuration,
      delay: cityLabelPlaneCenterTriangleDelay
    },

    triangleSize: {
      start: 65,
      end: 0,
      duration: cityLabelPlaneTriangleDuration,
      delay: cityLabelPlaneTriangleDelay
    },

    triangleOpacity: {
      start: 0,
      end: 0.8,
      duration: cityLabelPlaneTriangleOpacityDuration,
      delay: cityLabelPlaneTriangleOpacityDelay
    },

    triangleEdgeCicleSize: {
      start: 65,
      end: triangleCircleSize,
      duration: cityLabelPlaneTriangleEdgeCicleDuration,
      delay: cityLabelPlaneTriangleEdgeCicleDelay
    },

    triangleEdgeCicleRotate: {
      start: 0,
      end: Math.PI * 2,
      infinity: true,
      duration: cityLabelPlaneTriangleEdgeCicleRotateDuration,
      delay: cityLabelPlaneTriangleEdgeCicleRotateDeley
    },

    flyLine: {
      start: 0,
      end: 200,
      duration: flyLineDuration,
      delay: flyLineDelay
    },
    
    cameraFrustumSize: {
      start: 3,
      end: 10,
      mediacies: [[0, cameraMove1Duration, 10], [FRAME24]],
      delay: cameraMoveDelay,
      duration: cameraMoveDuration
    },

    cameraPX: {
      start: 10,
      end: 31.9,
      mediacies: [[0, cameraMove1Duration, 29.9], [FRAME24]],
      delay: cameraMoveDelay,
      duration: cameraMoveDuration
    },
    cameraPY: {
      start: 20,
      end: 31.7,
      mediacies: [[0, cameraMove1Duration, 41.8], [FRAME24]],
      delay: cameraMoveDelay,
      duration: cameraMoveDuration
    },
    cameraPZ: {
      start: 5,
      end: 15.3,
      mediacies: [[0, cameraMove1Duration, 19.6], [FRAME24]],
      delay: cameraMoveDelay,
      duration: cameraMoveDuration
    },
    cameraLookAtX: {
      start: 0,
      end: -11,
      mediacies: [[0, cameraMove1Duration, -6.1], [FRAME24]],
      delay: cameraMoveDelay,
      duration: cameraMoveDuration
    },
    cameraLookAtY: {
      start: 6.7,
      end: -11.7,
      mediacies: [[0, cameraMove1Duration, -12.6], [FRAME24]],
      delay: cameraMoveDelay,
      duration: cameraMoveDuration
    },
    cameraLookAtZ: {
      start: 0,
      end: -7.6,
      mediacies: [[0, cameraMove1Duration, -7.7], [FRAME24]],
      delay: cameraMoveDelay,
      duration: cameraMoveDuration
    }
  }

  class app { //为了以后开发的东西能直接到项目里用，所以最好写成类

    constructor(el, loadObjMtl) {
      this.el = el;

      var params = {
        time: [-1, FRAME24],
        lightIntensity: [0.6, 0.1],
        cameraFrustumSize: 10,
        cameraPX: [29.9, 0.1],
        cameraPY: [41.8, 0.1],
        cameraPZ: [19.6, 0.1],
        cameraLookAtX: [-6.1, 0.1],
        cameraLookAtY: [-12.6, 0.1],
        cameraLookAtZ: [-7.7, 0.1]
      };
      
      var constantProps = null;
      manualControlHelp(params, () => { this.step() }, el)

      var animate = new animation({
        props: _.merge({}, animateProps, constantProps),
        duration: 1000
      })

      this.params = params
      this.constantProps = constantProps
      this.animate = animate;

      this.timer = null;
      this.t = 0;

      this.w = 960;
      this.h = 540;
      this.aspect = this.w / this.h;

      // var camera, scene, renderer, light, drLight;
      // var clock = new THREE.Clock()

      this.oStore = {};

      var frameProps = animate.getStaticFrame(0)

      this.initRenderer();
      $('.webgl').append(this.renderer.domElement);
      this.initScene();
      this.initCamera(frameProps);
      this.initLight();

      const objName = 'hubei';
      let requestMeshPromises = []
      requestMeshPromises.push(loadObjMtl(objName, objName));

      this.oStore.textures = []

      Promise.all(requestMeshPromises).then(res => {
        let obj3Ds = {}
        _.each(res, function(data) {
          obj3Ds[data.mark] = data.data;
        })

        var c = cities[0].point;

        //
        var map = obj3Ds[objName].clone();
        this.scene.add(map);
        this.oStore.map = map;

        //
        this.createDownLines(c);

        this.createDownLinesBg(c);

        this.createRing(c);

        this.createCityEffect(cities)

        this.createFlightLines(cities)

        // this.step()
      });

      this.step()
    }

    lightPointTexture(color) {
      const canvas = brightPointCanvas(color)

      const texture = new THREE.Texture(canvas)
      texture.needsUpdate = true;
      return texture;
    }

    createDownLines(point) {
      this.oStore.downLines = []

      var group = new THREE.Object3D()

      this.scene.add(group)
      group.position.set(point[0], point[1], 15)

      for (var i = 0; i < 6; i++) { //不定数量点
        var keyPoints = [
          new THREE.Vector3(random(-0.8, 0.8), random(-0.8, 0.8), 15),
          new THREE.Vector3(random(-0.5, 0.5), random(-0.5, 0.5), 10),
          new THREE.Vector3(random(-0.8, 0.8), random(-0.8, 0.8), 6),
          new THREE.Vector3(random(-0.5, 0.5), random(-0.5, 0.5), 3),
          new THREE.Vector3(0, 0, 0)
        ]
        keyPoints.reverse()
        //Create a closed wavey loop
        var curve = new THREE.CatmullRomCurve3(keyPoints);

        //第一条线为定数量点
        var count = i === 0 ? (downLineGroupMinCount * 2 - 1) : random(downLineGroupMinCount, downLineGroupMinCount * 2)

        var points = curve.getPoints(count);
        var geometry = new THREE.BufferGeometry().setFromPoints(points);

        // var material = new THREE.LineBasicMaterial({ color: 0xec494e });

        // Create the final object to add to the scene
        // var curveObject  = new THREE.Line(geometry, material);

        // group.add(curveObject)

        var particals = this.createMapPaticals(geometry, null, random(8, 12))

        group.add(particals);

        this.oStore.downLines.push({ //下降线点集
          mesh: particals
        })
      }

      var brightPointsCount = downLineGroupMinCount / 3

      for (var i = 0; i < 3; i++) {
        var vertices = [];
        for (var j = 0; j < brightPointsCount; j++) {
          var x = advRandom([{
            min: -0.3,
            max: 0.3,
            weight: 80
          }, {
            min: -2,
            max: 2,
            weight: 20
          }]);
          var y = advRandom([{
            min: -0.3,
            max: 0.3,
            weight: 80
          }, {
            min: -2,
            max: 2,
            weight: 20
          }]);
          var z = (j + random(-1, 1)) * 10 / brightPointsCount;
          vertices.push(x, y, z);
        }
        var geometry = new THREE.BufferGeometry();
        geometry.addAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));

        var brightPoints = this.createMapPaticals(geometry, null, random(2, 10))

        group.add(brightPoints);

        this.oStore.downLines.push({ //下降线附近分散点 3种不同大小
          mesh: brightPoints,
          isBrightPoints: true
        })
      }

      this.saveParticalsPosition(this.oStore.downLines)

      this.oStore.downLineGroup = group;
    }

    saveParticalsPosition(storeData) {
      _.each(storeData, item => {
        var mesh = item.mesh;
        if (mesh.isPoints) {
          var geometry = mesh.geometry
          var bufferAttribute = geometry.getAttribute('position')
          geometry.addAttribute('intialPosition', bufferAttribute.clone())

          // if (item.isBrightPoints) { // brightPoints
          //   var { count } = bufferAttribute

          //   var p0 = [
          //     bufferAttribute.getX(0),
          //     bufferAttribute.getY(0),
          //     bufferAttribute.getZ(0)
          //   ]

          //   for (var j = 0; j < count; j++) {
          //     bufferAttribute.setXYZ(j, p0[0], p0[1], p0[2])
          //   }
          // }
        }
      })
    }

    createDownLinesBg(point) {
      var geometry = new THREE.PlaneBufferGeometry(15, 2, 300, 10);

      var mesh = this.createPaticals(geometry, '#93395f', 1.0)

      mesh.rotation.y = Math.PI / 2
      mesh.position.z = 15 / 2
      var group = new THREE.Object3D()
      group.add(mesh)

      this.scene.add(group)
      group.position.set(point[0], point[1], 0)
    }

    createRing(point) {
      var geometry = new THREE.RingBufferGeometry(1, 2, 100, 10, 0, Math.PI * 2);

      var mesh = this.createPaticals(geometry, '#93395f', 1.0)

      var group = new THREE.Object3D()
      group.add(mesh)

      this.scene.add(group)
      group.position.set(point[0], point[1], 0)
    }

    createCityEffect(cities) {
      this.oStore.cityRsources = []
      for (var i = 0; i < cities.length; i++) {
        var group = new THREE.Object3D()
        var city = cities[i]
        var point = city.point

        var floorGeometry = new THREE.PlaneBufferGeometry(4, 4)
        var floorMaterial = new THREE.MeshBasicMaterial({
          map: this.createFloorTexture(i),
          transparent: true,
          depthTest: false
        })
        var floor = new THREE.Mesh(floorGeometry, floorMaterial)
        group.add(floor)

        this.oStore.cityRsources[i] = []
        this.createLabelLine(city, i, group)
        this.createLabel(city, i, group)

        group.position.set(point[0], point[1], 0)
        this.scene.add(group)
      }
    }

    createFlightLines(cities) {
      var startPoint = cities[0].point;
      this.oStore.flyLines = []

      for (var i = 1; i < cities.length; i++) {
        var endPoint = cities[i].point
        var centerX = (startPoint[0] + endPoint[0]) / 2
        var centerY = (startPoint[1] + endPoint[1]) / 2
        var keyPoints = [
          new THREE.Vector3(startPoint[0], startPoint[1], 0),
          new THREE.Vector3(centerX, centerY, 2),
          new THREE.Vector3(endPoint[0], endPoint[1], 0)
        ]

        var curve = new THREE.CatmullRomCurve3(keyPoints);

        var points = curve.getPoints(200);
        var geometry = new THREE.BufferGeometry().setFromPoints(points);

        var curveObject = this.createPaticals(geometry, 0x9a1c43, 5, 0)

        this.oStore.flyLines.push({
          mesh: curveObject
        })

        this.scene.add(curveObject)
      }

      this.saveParticalsPosition(this.oStore.flyLines)
    }

    createFloorTexture(i) {
      var lc = new littleCanvas()

      var frameProps = this.animate.getStaticFrame(0);

      this.drawFloorCanvas(lc, frameProps);

      const texture = new THREE.CanvasTexture(lc.pop())
      this.oStore.textures.push(texture)

      return texture;
    }

    drawFloorCanvas(lc, frameProps) {

      lc.clear()

      lc.draw(function(ctx) { //中心光点
        ctx.arc(this.size / 2, this.size / 2, 3, 0, 2 * Math.PI)
        ctx.fillStyle = graphUtil.joinRgb([181, 56, 80], frameProps.centerPoint.now)
        ctx.shadowColor = 'rgba(181, 56, 80, 1.0)';
        ctx.shadowBlur = 10;
        ctx.fill()
      })

      lc.draw(function(ctx) { // 中间三角形路径运动

      })

      //252, 247, 224

      var len = frameProps.triangleSize.now
      if (len) {
        lc.draw(function(ctx) { //缩小三角形
          var size = this.size
          var len = frameProps.triangleSize.now;
          ctx.moveTo(size / 2 - Math.sin(Math.PI / 3) * len, size / 2 - Math.cos(Math.PI / 3) * len)
          ctx.lineTo(size / 2 + Math.sin(Math.PI / 3) * len, size / 2 - Math.cos(Math.PI / 3) * len)
          ctx.lineTo(size / 2, size / 2 + len)
          ctx.fillStyle = graphUtil.joinRgb([252, 247, 224], frameProps.triangleOpacity.now)
          ctx.fill()
        })
      }


      lc.draw(function(ctx) { //随三角形缩小三段外圈
        ctx.arc(this.size / 2, this.size / 2, frameProps.triangleEdgeCicleSize.now, 0, 2 * Math.PI)
        ctx.strokeStyle = graphUtil.joinRgb([252, 247, 224], frameProps.triangleOpacity.now)
        ctx.lineWidth = 1
        ctx.stroke()
      })


      if (frameProps.triangleEdgeCicleRotate.started) {
        lc.draw(function(ctx) { //旋转外圈
          var gradient = ctx.createLinearGradient(0, -triangleCircleSize, 0, triangleCircleSize);
          gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
          gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');

          ctx.translate(this.size / 2, this.size / 2);
          ctx.rotate(frameProps.triangleEdgeCicleRotate.now);
          ctx.arc(0, 0, triangleCircleSize, 3 * Math.PI / 2, Math.PI / 2, true);
          ctx.lineWidth = 1;
          ctx.strokeStyle = gradient;
          ctx.lineCap = 'round';
          ctx.stroke();
        })
      }

    }

    createLabelLine(city, i, group) {
      var material = new THREE.LineBasicMaterial({
        color: 0xffffff,
        opacity: 0.8,
        transparent: true
      });

      var geometry = new THREE.Geometry();
      geometry.vertices.push(
        new THREE.Vector3(0, 0, 0)
      )
      this.drawPillar(geometry, 0)
      
      var line = new THREE.Line(geometry, material);
      group.add(line);

      this.oStore.cityRsources[i][0] = geometry

      var geometry2 = new THREE.PlaneBufferGeometry(0.1, 0.3)
      var material2 = new THREE.LineBasicMaterial({
        color: 0xffffff,
        opacity: 0.8,
        transparent: true
      });
      var plane = new THREE.Mesh(geometry2, material2)
      plane.rotation.x = -Math.PI / 2
      plane.rotation.y = Math.PI / 7
      
      this.drawPillarHead(plane, 0, 0)

      group.add(plane)

      this.oStore.cityRsources[i][1] = plane
    }

    drawPillar(geometry, height) {
      geometry.vertices[1] = new THREE.Vector3(0, 0, height)
      geometry.verticesNeedUpdate = true;
    }

    drawPillarHead(plane, height, scale) {
      plane.position.z = height
      if (scale === 0) {
        plane.material.opacity = 0;
      } else {
        plane.material.opacity = 0.8;
        plane.scale.set(scale, scale, scale) //缩放0会报警告
      }
    }

    createLabel(city, i, group) {
      var geometry = new THREE.PlaneBufferGeometry(2, 2);
      var material = new THREE.MeshBasicMaterial({
        map: this.createLabelTexture(city, i),
        transparent: true,
        depthTest: false
      });

      this.oStore.cityRsources[i][2] = material.map;

      var mesh = new THREE.Mesh(geometry, material);
      mesh.position.set(0, 0, 2.5)
      mesh.rotation.x = -Math.PI / 2
      mesh.rotation.y = Math.PI / 7
      mesh.rotation.z = Math.PI

      // let axesHelper = new THREE.AxesHelper(10);
      // mesh.add(axesHelper);

      group.add(mesh)

      // var spriteMap = createLabelTexture(city, i);
      // var spriteMaterial = new THREE.SpriteMaterial({ map: spriteMap });

      // var sprite = new THREE.Sprite(spriteMaterial);
      // sprite.position.set(0, 0, 2)
      // sprite.scale.set(1.7, 1.7, 1)

      // group.add(sprite)
    }

    createLabelTexture(city, i) {
      var canvas = document.createElement('canvas')
      const size = 128
      canvas.width = size
      canvas.height = size;

      var frameProps = this.animate.getStaticFrame(0)

      this.drawLabel(canvas, frameProps, city, i)

      const texture = new THREE.CanvasTexture(canvas)

      return texture;
    }

    drawLabel(canvas, frameProps, city, i) {
      const ctx = canvas.getContext('2d')
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      
      //背景
      ctx.save();
      ctx.beginPath()
      var color = i === 0 ? [64, 150, 178] : [29, 78, 127]
      ctx.fillStyle = graphUtil.joinRgb(color, frameProps.cityLabelBgOpacity.now);
      ctx.fillRect(8, 7, 120 - 8, 38 - 7);
      ctx.closePath()
      ctx.restore()

      //上半部分高光
      ctx.save();
      ctx.beginPath()
      ctx.fillStyle = graphUtil.joinRgb([255, 255, 255], 0.3 * frameProps.cityLabelBgOpacity.now);
      ctx.fillRect(8, 7, 120 - 8, 22.5 - 7);
      ctx.closePath()
      ctx.restore()

      //底部高亮
      ctx.save();
      ctx.beginPath()
      ctx.moveTo(8, 38)
      ctx.lineTo(120, 38)
      var linearGradient = ctx.createLinearGradient(0, 0, 120, 0);
      linearGradient.addColorStop(0, 'rgba(253,253,255,0)')
      linearGradient.addColorStop(0.5, graphUtil.joinRgb([253,253,255],frameProps.cityLabelBgOpacity.now));
      linearGradient.addColorStop(1, 'rgba(253,253,255,0)')
      ctx.strokeStyle = linearGradient;
      ctx.lineWidth = 2
      ctx.stroke();
      ctx.closePath()
      ctx.restore()

      //夹子
      ctx.save();
      ctx.beginPath()
      ctx.moveTo(5, 32)
      ctx.lineTo(5, 41)
      ctx.lineTo(16, 41)
      ctx.moveTo(112, 4)
      ctx.lineTo(123, 4)
      ctx.lineTo(123, 13)
      ctx.strokeStyle = graphUtil.joinRgb([255, 255, 255], 0.8 * frameProps.cityLabelBorder.now);
      ctx.lineWidth = 1
      ctx.shadowColor = graphUtil.joinRgb([255, 255, 255], frameProps.cityLabelBorder.now);
      ctx.shadowBlur = 2
      ctx.stroke();
      ctx.closePath()
      ctx.restore()

      //文字
      ctx.save();
      ctx.beginPath();
      var linearGradient = ctx.createLinearGradient(0, 0, 120, 0);
      linearGradient.addColorStop(0, 'rgba(253,253,255,1)')
      linearGradient.addColorStop(frameProps.cityLabelText.now, 'rgba(253,253,255,1)');
      linearGradient.addColorStop(Math.min(1, frameProps.cityLabelText.now + 0.2), 'rgba(253,253,255,0)');
      linearGradient.addColorStop(1, 'rgba(253,253,255,0)')
      ctx.fillStyle = linearGradient;
      ctx.textBaseline = 'middle';
      ctx.textAlign = 'center';
      ctx.font = '20px sans';
      ctx.fillText(city.name + '市', 64, 22.5);
      ctx.closePath();
      ctx.restore();
    }

    createPaticals(geometry, color, size, opacity) {
      var pointsMaterial = new THREE.PointsMaterial({
        color: color || 0xc9b173,
        size: size || 2.0,
        depthTest: false,
        opacity: opacity || opacity === 0 ? opacity : 1.0,
        transparent: true
      })
      var mesh = new THREE.Points(geometry, pointsMaterial)
      return mesh;
    }

    createMapPaticals(geometry, color, size) {
      var pointsMaterial = new THREE.PointsMaterial({
        size: size || 2.0,
        map: this.lightPointTexture('#c9b173'),
        blending: THREE.AdditiveBlending,
        depthTest: false,
        transparent: true
      })
      var mesh = new THREE.Points(geometry, pointsMaterial)
      return mesh;
    }

     initRenderer() {
      if (!this.renderer) {
        this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
        this.renderer.setSize(this.w, this.h);
        // this.renderer.setClearColor(0x000000, 1.0);
        this.renderer.setPixelRatio(window.devicePixelRatio)

        // this.renderer.shadowMap.enabled = true;
        // this.renderer.shadowMapType = THREE.PCFSoftShadowMap;
      } else {
        this.renderer.setSize(this.w, this.h)
      }
      return this.renderer;
    }

    initScene() {
      if (!this.scene) {

        this.scene = new THREE.Scene()

        let axesHelper = new THREE.AxesHelper(500);
        this.scene.add(axesHelper);

        var helper = new THREE.GridHelper(1000, 100);
        helper.rotation.x = Math.PI / 2; //默认不是x,y平面
        helper.material.opacity = 0.25;
        helper.material.transparent = true;
        this.scene.add(helper);
      }

      return this.scene;
    }

    initCamera(frameProps) {
      var params = {};
      if (1) {
        _.each(frameProps, function(item, param) {
          params[param] = item.now;
        })
      } else {
        params = this.params;
      }
      
      const frustumSize = params.cameraFrustumSize
      const near = 0.01;
      const far = mathUtil.vector3Length(params.cameraPX, params.cameraPY, params.cameraPZ) * 2;
      if (!this.camera) {
        this.camera = new THREE.OrthographicCamera(
          frustumSize * this.aspect / -2,
          frustumSize * this.aspect / 2,
          frustumSize / 2,
          frustumSize / -2, near, far)

          // var helper = new THREE.CameraHelper(this.camera);
          // this.scene.add(helper);
      } else {
        this.camera.left = frustumSize * this.aspect / -2
        this.camera.right = frustumSize * this.aspect / 2
        this.camera.top = frustumSize / 2
        this.camera.bottom = frustumSize / -2
        this.camera.near = near
        this.camera.far = far
      }
//       console.log('camera near:', near, 'far:', far, 'x*y:', frustumSize * this.aspect, 'x', frustumSize)
      this.camera.position.set(params.cameraPX, params.cameraPY, params.cameraPZ)
      this.camera.up.set(0, 0, 1)
      this.camera.lookAt(params.cameraLookAtX, params.cameraLookAtY, params.cameraLookAtZ)
      this.camera.updateProjectionMatrix()
      return this.camera;
    }

    initLight() {
      var params = this.params;
      if (!this.light) {
        this.light = new THREE.AmbientLight(0xffffff);
        this.light.position.set(100, 100, 100)
        this.scene.add(this.light)
      }

      if (!this.drLight) {
        this.drLight = new THREE.DirectionalLight(0xffffff, params.lightIntensity);
        this.drLight.position.set(100, 100, 0).normalize() // normalize方法？
        // this.drLight.position.set(100, 100, 0)

        if (1) {
          this.scene.add(this.drLight)
        } {
          // 课程22里面是这样的,不知道是为什么?说是模拟光是从相机的位置发出
          this.camera.add(this.drLight);
          this.camera.add(this.drLight.target);
          this.scene.add(this.camera);
        }

      } else {
        this.drLight.intensity = params.lightIntensity
      }
    }

    step() {
      if (this.timer) {
        cancelAnimationFrame(this.timer)
      }
      this.timer = requestAnimationFrame(() => { this.step() });

      var params = this.params;
      var animate = this.animate;
      var constantProps = this.constantProps;

      var frameProps = animate.getStaticFrame(this.t)

      if (constantProps) {

        _.each(params, (val, param) => {
          params[param] = frameProps[param].now;
        })

        if (this.t > (animate.option.delay + animate.option.duration)) {
          constantProps = null
        }
      }

      this.initCamera(frameProps);

      this.initLight()

      if (this.oStore.downLineGroup) {

        this.oStore.downLineGroup.position.z = frameProps.downHeight.now;

        // oStore.downLineGroup.rotation.z += 0.05

        var len = this.oStore.downLines && this.oStore.downLines.length;

        for (var i = 0; i < len; i++) {
          var downLine = this.oStore.downLines[i];

          var mesh = downLine.mesh;

          var bufferAttribute = mesh.geometry.getAttribute('position')
          var { count } = bufferAttribute
          var intialBufferAttribute = mesh.geometry.getAttribute('intialPosition')

          if (downLine.isBrightPoints) {
            mesh.material.opacity = frameProps.brightPointsOpacity.now;
            bufferAttribute.needsUpdate = true
          } else {
            for (var k = 0; k < count; k++) {
              if (k <= frameProps.eraseLinePointsCount.now) {
                bufferAttribute.setXYZ(
                  count - k,
                  intialBufferAttribute.getX(0),
                  intialBufferAttribute.getY(0),
                  intialBufferAttribute.getZ(0)
                )
                mesh.material.opacity = 0;
              } else {
                bufferAttribute.setXYZ(
                  count - k,
                  intialBufferAttribute.getX(count - k),
                  intialBufferAttribute.getY(count - k),
                  intialBufferAttribute.getZ(count - k)
                )
                mesh.material.opacity = 1;
              }
            }

            mesh.material.needsUpdate = true;
            bufferAttribute.needsUpdate = true
          }
        }

      }

      for (var i = 0; i < this.oStore.textures.length; i++) {
        var texture = this.oStore.textures[i]
        var frameProps1 = animate.getStaticFrame(Math.max(0, this.t - cityLabelInterval * i))
        this.drawFloorCanvas(new littleCanvas(texture.image), frameProps1)
        texture.needsUpdate = true

        this.drawPillar(this.oStore.cityRsources[i][0], frameProps1.pillarHeight.now)
        this.drawPillarHead(this.oStore.cityRsources[i][1], frameProps1.pillarHeight.now, frameProps1.pillarHeadScale.now)

        var labelTexture = this.oStore.cityRsources[i][2];
        this.drawLabel(labelTexture.image, frameProps1, cities[i], i)
        labelTexture.needsUpdate = true
      }

      const flyLineLength = this.oStore.flyLines && this.oStore.flyLines.length
      for (var i = 0; i < flyLineLength; i++) {
        var flyLine = this.oStore.flyLines[i];
        var mesh = flyLine.mesh;
        var bufferAttribute = mesh.geometry.getAttribute('position')
        var intialBufferAttribute = mesh.geometry.getAttribute('intialPosition')
        var { count } = bufferAttribute
        
        for (var k = 0; k < count; k++) {
          if (k < frameProps.flyLine.now) {
            bufferAttribute.setXYZ(
              k,
              intialBufferAttribute.getX(k),
              intialBufferAttribute.getY(k),
              intialBufferAttribute.getZ(k)
            )
          } else {
            bufferAttribute.setXYZ(
              k,
              intialBufferAttribute.getX(frameProps.flyLine.now),
              intialBufferAttribute.getY(frameProps.flyLine.now),
              intialBufferAttribute.getZ(frameProps.flyLine.now)
            )
          }
        }

        if (frameProps.flyLine.now === 0) {
          mesh.material.opacity = 0;
        } else {
          mesh.material.opacity = 0.4;
        }

        mesh.material.needsUpdate = true;
        bufferAttribute.needsUpdate = true
      }

      this.render()

      if (params.time >= 0) {
        this.t = params.time
      } else {
        this.t += FRAME60;
      }

      if (this.t > 120000) {
        this.t -= 60000;
      }
    }

    render() {
      this.renderer.render(this.scene, this.camera);
    }
  }


  function random(a, b) {
    return Math.random() * (b - a) + a;
  }

  // [{ min, max, weight }]
  function advRandom(options) {
    var weights = [0]
    _.each(options, item => {
      var len = weights.length;
      weights[len] = weights[len - 1] + item.weight;
    })
    // [0, 50, 80, 100]
    var weightsLen = weights.length
    var num = Math.random() * weights[weightsLen - 1];
    var pos = 1;
    for (pos = 1; pos < weightsLen; pos++) {
      if (num < weights[pos]) {
        break;
      }
    }
    var option = options[pos - 1]
    return random(option.min, option.max);
  }

  return main;
})
